Go での package 設計
layered な設計をやめたい
- - -
class 設計、module 設計の氣持ちで package を設計してる
code:package の依存關係.mmd
flowchart TB
subgraph MainApp
main --> server1
main --> server2
main --> config
main --> db1
main --> db2
main --> logger
server1 --> interactor
server1 --> entity
server2 --> interactor
server2 --> entity
interactor --> db1
interactor --> db2
interactor --> entity
db1 --> entity
db2 --> entity
end
main -.-> OSSignal
config -.-> ENV
logger -.-> STDOUT
server1 -.-> Port1
server2 -.-> Port2
db1 -.-> DB1
db2 -.-> DB2
entity package には、どこででも使へる型を入れる。最も安定した package
最近では domain って package 名にした
main package は起動と終了を扱ふ。最も不安定な package
graceful shutdown
context の情報を持つものは context に入れて渡す
logger 等
code:logger.go
type Logger ろがー
type LogLevel int
const (
Fatal LogLevel = 0
Error
Warn
Info
Debug
Trace
)
func New() Logger {}
func (log Logger) SetLogLevel(logLevel LogLevel) {}
type logger struct { }
func (l *Logger) WithLogger(ctx context.Context) context.Context {
return context.WithValue(ctx, logger{}, l)
}
func FromContext(ctx context.Context) (*Logger, bool) {
logger, ok := ctx.Value(logger{}).(*Logger)
return logger, ok
}
code:db1.go
package db1
// DB の connection pool は singleton を作って DB package に閉じ込める
var conn DBConn
type DB1 struct {}
func New(dsn string) error {
conn = createDB1Conn()
return nil
}
code:config.go
type struct Config {
DB1ConnInfo string
LogLevel int
}
func LoadConfig() (*Config, error) {
return &Config{}, nil
}
code:main.go
import (
"〜/config"
"〜/logger"
)
func main() {
// 初期化中の error を log に吐く爲、最初に初期化する
log := logger.New()
cfg, err := config.LoadConfig()
if err != nil {
log.Fatalln("Failed to load config.", err)
}
log.SetLogLevel(cfg.LogLevel)
err := db1.New(cfg.DB1ConnInfo)
if err != nil {
log.Fatalln("Failed to connect DB1.", err)
}
}
config
config は起動時に初期化し、以後變化しない
ErlangErlang.icon なら起動中に變へる術を考へるけどね GenServer や ETS から提供し、常にその GenServer や ETS から讀み出す
Supervisor tree を再起動する
TCP 接續は再起動を跨いで持ち越せるのだ
config を各 package に渡す時は、各 package が求める型に變換して渡す
config を丸ごと渡さない
欲しい型を各 package が提示する
1 つの外部 resource には 1 つの package で應對する
外部 resource の都合を餘り外に漏らすな
サボって、interactor の依存を逆轉してゐないから…
interactor 以外が外部 resource に依存しさうになったらやるといい
複數の外部 resource は interactor package で協働させる
最近では usecase って package 名にした
intercator は main の次に不安定な package
interactor から interactor を呼んでもいい
interactor から依存するのではなく、interactor に依存する樣に逆轉させた方がいいのかなぁ?
code:db/db.go
import (
"〜/interactor"
)
type struct DB {}
func New() *interactor.DB {
return &DB{}
}
func (db *DB) Create(ctx context.Context, arg Arg) (Result, error) {}
code:interactor/useDB.go
type DB interface {
Create(ctx context.Context, arg Arg) (Result, error)
}
func doCreate(ctx context.Context, db *DB, arg Arg) (Result, error) {
result, err := db.Create(ctx, arg)
}
やる氣無い
interactor 以外が外部 resource に依存しさうになったらやるといい